using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml.Serialization;

namespace R2ML
{
    public sealed class RuleFactory
    {
        #region Singleton Pattern
		static readonly RuleFactory instance=new RuleFactory();

        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static RuleFactory()
        {
        }

        RuleFactory()
        {
            //create the rulebase here

            _rulebase = new RuleBase();

            //set up the rulebase with the basics of what we require.

            _rulebase.ruleSet = new ReactionRuleSet();
        }

        public static RuleFactory Instance
        {
            get
            {
                return instance;
            }
        } 
	    #endregion

        public RuleBase ruleBase
        {
            get { return _rulebase; }
            set { _rulebase = value; }
        }

        #region add ReactionRule
        public int addReactionRule()
        {
            return addReactionRule("");
        }

        public int addReactionRule(String ruleID)
        {
            ReactionRule rr = new ReactionRule();
            rr.ruleID = ruleID;

            //add a reactionrule and return the position in the rule list of it.
            _rulebase.ruleSet.reactionRuleList.Add(rr);

            return _rulebase.ruleSet.reactionRuleList.Count;
        } 
        #endregion

        #region ReactionRule Properties
        public void addReactionRuleDocumentation(int ruleNumber)
        {
            throw new Exception("Method not implemented yet");
        }

        private ReactionRule getReactionRulebyIndex(int index)
        {
            //get rule specified by ruleNumber
            return _rulebase.ruleSet.reactionRuleList[index-1];
        }

        #region addReactionRuleTriggeringEvent

        public void addReactionRuleTriggeringEvent(int ruleNumber, String senderXpath)
        {
            //TODO: convert the query expression to a URI.
            

            //get rule specified by ruleNumber
            getReactionRulebyIndex(ruleNumber).triggeringEvent.messageEventExpression.sender = senderXpath;
        }

        public void addReactionRuleTriggeringEvent(int ruleNumber, List<String> eventType)
        {
            String concatinator = "";
            int counterVar = 0;
            foreach (String str in eventType)
            {
                ++counterVar;

                concatinator += str;
                if (counterVar < eventType.Count)
                {
                    concatinator += ";";
                }
            }

            //get rule specified by ruleNumber
            getReactionRulebyIndex(ruleNumber).triggeringEvent.messageEventExpression.eventType = concatinator;
        }

        public void addReactionRuleTriggeringEvent(int ruleNumber, String senderXpath, List<String> eventType)
        {
            //startTime="now" //these are not in r2ml 0.4
            //duration="0"    //these are not in r2ml 0.4
            //
            //ex1.
            //String eventType="mousedown;mouseup"; Semi colon seems conventional for separating list items. see XAML namespacing for example. Also consider java/c++/C# as examples too.
            //String sender="button1;button2"; The sender will be the xpath part because the sender may be a query and not have set objects.
            //
            //arguments. Can we work out what these are based on the eventType?

            addReactionRuleTriggeringEvent(ruleNumber, senderXpath);
            addReactionRuleTriggeringEvent(ruleNumber, eventType);

        } 
        #endregion

        public void addReactionRuleConditions(int ruleNumber, String conditionXpath)
        {
            //example of what a condition might be.
            //if condition is true then procede otherwise do not fire action.
            //
            //ex.1  if printing != true

            ReactionRule rr = getReactionRulebyIndex(ruleNumber);
            rr.conditions = conditionXpath;
        }

        #region AddReactionRuleProducedAction

        public void addReactionRuleProducedAction(int ruleNumber, String actionQuery)
        {
            if (actionQuery == null) { throw new Exception("actionQuery parameter was null"); }

            ReactionRule rr = getReactionRulebyIndex(ruleNumber);

            if (rr.producedAction.assignActionExpression != null)
            {
                //our rule is an assignment rule.
                rr.producedAction.assignActionExpression.contextArguement[0] = new ObjectVariable();
                rr.producedAction.assignActionExpression.contextArguement[0].name = actionQuery;
            }
            else if (rr.producedAction.invokeActionExpression != null)
            {
                //our rule is a method call.
                rr.producedAction.invokeActionExpression.contextArguement[0] = new ObjectVariable();
                rr.producedAction.invokeActionExpression.contextArguement[0].name = actionQuery;
                
            }
            else
            {
                //assume our rule is an assignment rule unless specified otherwise.
                rr.producedAction.assignActionExpression.contextArguement = new ObjectVariable[1];
                rr.producedAction.assignActionExpression.contextArguement[0] = new ObjectVariable();
                rr.producedAction.assignActionExpression.contextArguement[0].name = actionQuery;
            }
        }

        public void addReactionRuleProducedAction(int ruleNumber, ActionType actionType, String actionCall)
        {

            ReactionRule rr = getReactionRulebyIndex(ruleNumber);

            if (actionType == ActionType.method)
            {
                //if we have data in an assignment property then move it to a method property
                if (rr.producedAction.assignActionExpression != null)
                {
                    rr.producedAction.invokeActionExpression = new ProducedAction.InvokeActionExpression();
                    rr.producedAction.invokeActionExpression.arguements = new TypedLiteral[1];
                    rr.producedAction.invokeActionExpression.arguements[0] = new TypedLiteral();
                    rr.producedAction.invokeActionExpression.contextArguement = new ObjectVariable[1];
                    rr.producedAction.invokeActionExpression.contextArguement[0] = new ObjectVariable();

                    rr.producedAction.invokeActionExpression.arguements[0].lexicalValue = rr.producedAction.assignActionExpression.arguements[0].lexicalValue;
                    rr.producedAction.invokeActionExpression.arguements[0].datatypeID = rr.producedAction.assignActionExpression.arguements[0].datatypeID;
                    rr.producedAction.invokeActionExpression.contextArguement[0].name = rr.producedAction.assignActionExpression.contextArguement[0].name;
                    rr.producedAction.invokeActionExpression.operationID = actionCall;

                    //make sure the old data is removed and is not able to be serialized.
                    rr.producedAction.assignActionExpression = null;
                }
                else
                {
                    rr.producedAction.invokeActionExpression.operationID = actionCall;
                }
            }
            else if (actionType == ActionType.property)
            {
                //if we have data in a method property then move it to an assignment property
                if (rr.producedAction.invokeActionExpression != null)
                {
                    rr.producedAction.assignActionExpression = new ProducedAction.AssignActionExpression();
                    rr.producedAction.assignActionExpression.arguements = new TypedLiteral[1];
                    rr.producedAction.assignActionExpression.arguements[0] = new TypedLiteral();
                    rr.producedAction.assignActionExpression.contextArguement = new ObjectVariable[1];
                    rr.producedAction.assignActionExpression.contextArguement[0] = new ObjectVariable();

                    rr.producedAction.assignActionExpression.arguements[0].lexicalValue = rr.producedAction.invokeActionExpression.arguements[0].lexicalValue;
                    rr.producedAction.assignActionExpression.arguements[0].datatypeID = rr.producedAction.invokeActionExpression.arguements[0].datatypeID;
                    rr.producedAction.assignActionExpression.contextArguement[0].name = rr.producedAction.invokeActionExpression.contextArguement[0].name;
                    rr.producedAction.assignActionExpression.propertyID = actionCall;

                    //make sure the old data is removed and is not able to be serialized.
                    rr.producedAction.invokeActionExpression = null;
                }
                else
                {
                    rr.producedAction.assignActionExpression.propertyID = actionCall;
                }
            }
        }

        public void addReactionRuleProducedAction(int ruleNumber,List<String> types , List<String> parameter)
        {
            ReactionRule rr = getReactionRulebyIndex(ruleNumber);

            for (int i = 0; i < parameter.Count; i++)
            {
            
                if (rr.producedAction.assignActionExpression != null)
                {
                    //our rule is an assignment rule.
                    rr.producedAction.assignActionExpression.arguements[i] = new TypedLiteral();
                    rr.producedAction.assignActionExpression.arguements[i].lexicalValue = parameter[i];
                    rr.producedAction.assignActionExpression.arguements[i].datatypeID = types[i];

                }
                else if (rr.producedAction.invokeActionExpression != null)
                {
                    //our rule is a method call.
                    rr.producedAction.invokeActionExpression.arguements[i] = new TypedLiteral();
                    rr.producedAction.invokeActionExpression.arguements[i].lexicalValue = parameter[i];
                    rr.producedAction.invokeActionExpression.arguements[i].datatypeID = types[i];

                }
                else
                {
                    //assume our rule is an assignment rule unless specified otherwise.
                    rr.producedAction.assignActionExpression.arguements = new TypedLiteral[parameter.Count];
                    rr.producedAction.assignActionExpression.arguements[i] = new TypedLiteral();
                    rr.producedAction.assignActionExpression.arguements[i].lexicalValue = parameter[i];
                    rr.producedAction.assignActionExpression.arguements[i].datatypeID = types[i];
                }
            }
        }
        
        #endregion

        public void addReactionRulePostCondition(int ruleNumber, String xpath)
        {
            //scenario1. catch exceptions here
            //scenario2. Reflection (inpecting to see if the rule did something. the concept of reflection pretty much)
            //scenario3. 

            ReactionRule rr = getReactionRulebyIndex(ruleNumber);

            String[] parts = xpath.Split(';');
            
            for (int i = 0; i < parts.Length-1; i++)
			{
                rr.postcondition.objectClassificationAtom.arguements[i] = new ObjectVariable();
			    rr.postcondition.objectClassificationAtom.arguements[i].name = parts[i];
			}

            rr.postcondition.objectClassificationAtom.operationID = parts[parts.Length-1];
            
        } 
        #endregion

        /// <summary>
        /// Check to see if a rule list has been loaded. A rule list probably wants to be loaded/initialized before using this class.
        /// </summary>
        /// <returns></returns>
        public bool Initialized()
        {
            return _started;
        }

        public void LoadRules(String document)
        {
            //serialize the data
            Stream textstream = new MemoryStream(ASCIIEncoding.ASCII.GetBytes(document));

            XmlSerializer s = new XmlSerializer(typeof(RuleBase));

            _rulebase = (RuleBase)s.Deserialize(textstream);

            _started = true;
        }

        public String SerializeRules()
        {
            //serialize the data
            Stream textstream = new MemoryStream();

            XmlSerializer s = new XmlSerializer(typeof(RuleBase));

            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add("r2ml", "http://www.rewerse.net/I1/2006/R2ML");
            ns.Add("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
            ns.Add("dc", "http://purl.org/dc/elements/1.1/");
            ns.Add("r2mlv", "http://www.rewerse.net/I1/2006/R2ML/R2MLV");
            ns.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance");

            s.Serialize(textstream, RuleFactory.Instance.ruleBase,ns);

            string contents = "";
            textstream.Position = 0;

            using (StreamReader reader = new StreamReader(textstream))
            {
                contents = reader.ReadToEnd();
            }

            return contents;
        }

        public enum ActionType { method, property }

        bool _started = false;
        RuleBase _rulebase;
    }
}
